/*
 * Copyright 2012-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.http;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.http.converter.xml.AbstractXmlHttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.util.ClassUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**
 * 用于管理 Spring Boot 应用程序中使用的 {@link HttpMessageConverter} 的 Bean。
 * 提供了一种便捷的方式，可以向 Web 应用程序添加和合并额外的 {@link HttpMessageConverter}。
 * <p>
 * 如果需要，可以注册带有特定 {@link #HttpMessageConverters(HttpMessageConverter...) 附加转换器} 的此 Bean 实例，否则将使用默认转换器。
 * <p>
 * 注意：使用的默认转换器与标准 Spring MVC 相同（参见 {@link WebMvcConfigurationSupport}），但进行了一些细微的重新排序，将 XML 转换器放在列表的末尾。
 *
 * @author Dave Syer
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @since 2.0.0
 * @see #HttpMessageConverters(HttpMessageConverter...)
 * @see #HttpMessageConverters(Collection)
 * @see #getConverters()
 */
public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>> {

	private static final List<Class<?>> NON_REPLACING_CONVERTERS;

	static {
		List<Class<?>> nonReplacingConverters = new ArrayList<>();
		addClassIfExists(nonReplacingConverters,
				"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter");
		NON_REPLACING_CONVERTERS = Collections.unmodifiableList(nonReplacingConverters);
	}

	private static final Map<Class<?>, Class<?>> EQUIVALENT_CONVERTERS;

	static {
		Map<Class<?>, Class<?>> equivalentConverters = new HashMap<>();
		putIfExists(equivalentConverters, "org.springframework.http.converter.json.MappingJackson2HttpMessageConverter",
				"org.springframework.http.converter.json.GsonHttpMessageConverter");
		EQUIVALENT_CONVERTERS = Collections.unmodifiableMap(equivalentConverters);
	}

	private final List<HttpMessageConverter<?>> converters;

	/**
	 * 使用指定的附加转换器创建新的 {@link HttpMessageConverters} 实例。
	 * @param additionalConverters 要添加的附加转换器。这些项目会添加到任何相同类型的默认转换器之前（如果没有找到默认转换器，则添加到列表的最前面）。
	 * {@link #postProcessConverters(List)} 方法可用于进一步的转换器操作。
	 */
	public HttpMessageConverters(HttpMessageConverter<?>... additionalConverters) {
		this(Arrays.asList(additionalConverters));
	}

	/**
	 * 使用指定的附加转换器创建新的 {@link HttpMessageConverters} 实例。
	 * @param additionalConverters 要添加的附加转换器。这些项目会添加到任何相同类型的默认转换器之前（如果没有找到默认转换器，则添加到列表的最前面）。
	 * {@link #postProcessConverters(List)} 方法可用于进一步的转换器操作。
	 */
	public HttpMessageConverters(Collection<HttpMessageConverter<?>> additionalConverters) {
		this(true, additionalConverters);
	}

	/**
	 * 使用指定的转换器创建新的 {@link HttpMessageConverters} 实例。
	 * @param addDefaultConverters 是否应添加默认转换器
	 * @param converters 要添加的转换器。这些项目会添加到任何相同类型的默认转换器之前（如果没有找到默认转换器，则添加到列表的最前面）。
	 * {@link #postProcessConverters(List)} 方法可用于进一步的转换器操作。
	 */
	public HttpMessageConverters(boolean addDefaultConverters, Collection<HttpMessageConverter<?>> converters) {
		List<HttpMessageConverter<?>> combined = getCombinedConverters(converters,
				addDefaultConverters ? getDefaultConverters() : Collections.emptyList());
		combined = postProcessConverters(combined);
		this.converters = Collections.unmodifiableList(combined);
	}

	private List<HttpMessageConverter<?>> getCombinedConverters(Collection<HttpMessageConverter<?>> converters,
			List<HttpMessageConverter<?>> defaultConverters) {
		List<HttpMessageConverter<?>> combined = new ArrayList<>();
		List<HttpMessageConverter<?>> processing = new ArrayList<>(converters);
		for (HttpMessageConverter<?> defaultConverter : defaultConverters) {
			Iterator<HttpMessageConverter<?>> iterator = processing.iterator();
			while (iterator.hasNext()) {
				HttpMessageConverter<?> candidate = iterator.next();
				if (isReplacement(defaultConverter, candidate)) {
					combined.add(candidate);
					iterator.remove();
				}
			}
			combined.add(defaultConverter);
			if (defaultConverter instanceof AllEncompassingFormHttpMessageConverter) {
				configurePartConverters((AllEncompassingFormHttpMessageConverter) defaultConverter, converters);
			}
		}
		combined.addAll(0, processing);
		return combined;
	}

	private boolean isReplacement(HttpMessageConverter<?> defaultConverter, HttpMessageConverter<?> candidate) {
		for (Class<?> nonReplacingConverter : NON_REPLACING_CONVERTERS) {
			if (nonReplacingConverter.isInstance(candidate)) {
				return false;
			}
		}
		Class<?> converterClass = defaultConverter.getClass();
		if (ClassUtils.isAssignableValue(converterClass, candidate)) {
			return true;
		}
		Class<?> equivalentClass = EQUIVALENT_CONVERTERS.get(converterClass);
		return equivalentClass != null && ClassUtils.isAssignableValue(equivalentClass, candidate);
	}

	private void configurePartConverters(AllEncompassingFormHttpMessageConverter formConverter,
			Collection<HttpMessageConverter<?>> converters) {
		List<HttpMessageConverter<?>> partConverters = formConverter.getPartConverters();
		List<HttpMessageConverter<?>> combinedConverters = getCombinedConverters(converters, partConverters);
		combinedConverters = postProcessPartConverters(combinedConverters);
		formConverter.setPartConverters(combinedConverters);
	}

	/**
	 * 可用于在使用 {@link HttpMessageConverter} 列表之前对其进行后处理的方法。
	 * @param converters 将使用的可变转换器列表。
	 * @return 最终要使用的转换器列表
	 */
	protected List<HttpMessageConverter<?>> postProcessConverters(List<HttpMessageConverter<?>> converters) {
		return converters;
	}

	/**
	 * 可用于在使用 {@link HttpMessageConverter} 列表配置 {@link AllEncompassingFormHttpMessageConverter} 的部分转换器之前对其进行后处理的方法。
	 * @param converters 将使用的可变转换器列表。
	 * @return 最终要使用的转换器列表
	 * @since 1.3.0
	 */
	protected List<HttpMessageConverter<?>> postProcessPartConverters(List<HttpMessageConverter<?>> converters) {
		return converters;
	}

	private List<HttpMessageConverter<?>> getDefaultConverters() {
		List<HttpMessageConverter<?>> converters = new ArrayList<>();
		if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport",
				null)) {
			converters.addAll(new WebMvcConfigurationSupport() {

				public List<HttpMessageConverter<?>> defaultMessageConverters() {
					return super.getMessageConverters();
				}

			}.defaultMessageConverters());
		}
		else {
			converters.addAll(new RestTemplate().getMessageConverters());
		}
		reorderXmlConvertersToEnd(converters);
		return converters;
	}

	private void reorderXmlConvertersToEnd(List<HttpMessageConverter<?>> converters) {
		List<HttpMessageConverter<?>> xml = new ArrayList<>();
		for (Iterator<HttpMessageConverter<?>> iterator = converters.iterator(); iterator.hasNext();) {
			HttpMessageConverter<?> converter = iterator.next();
			if ((converter instanceof AbstractXmlHttpMessageConverter)
					|| (converter instanceof MappingJackson2XmlHttpMessageConverter)) {
				xml.add(converter);
				iterator.remove();
			}
		}
		converters.addAll(xml);
	}

	@Override
	public Iterator<HttpMessageConverter<?>> iterator() {
		return getConverters().iterator();
	}

	/**
	 * 返回一个不可变的转换器列表，列表中的转换器将按照注册顺序排列。
	 * @return 转换器列表
	 */
	public List<HttpMessageConverter<?>> getConverters() {
		return this.converters;
	}

	private static void addClassIfExists(List<Class<?>> list, String className) {
		try {
			list.add(Class.forName(className));
		}
		catch (ClassNotFoundException | NoClassDefFoundError ex) {
			// 忽略
		}
	}

	private static void putIfExists(Map<Class<?>, Class<?>> map, String keyClassName, String valueClassName) {
		try {
			map.put(Class.forName(keyClassName), Class.forName(valueClassName));
		}
		catch (ClassNotFoundException | NoClassDefFoundError ex) {
			// 忽略
		}
	}

}
